﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace Djs.Common.Components
{
    public class GScrollBar : InteractiveItem, IInteractiveItem
    {
        public GScrollBar()
        {
            this.SubItemsInit();
        }
        #region Public properties and events
        #region Bounds, InnerBounds
        /// <summary>
        /// Outer coordinates of scrollbar.
        /// Setting a coordinate will detect orientation by Width/Height relation.
        /// </summary>
        public Rectangle Bounds
        {
            get { return this._Bounds; }
            set
            {
                this._Bounds = value;
                this._Orientation = (value.Width >= value.Height ? System.Windows.Forms.Orientation.Horizontal : System.Windows.Forms.Orientation.Vertical);       // Autodetect Orientation
                this.InnerBoundsReset();
            }
        } private Rectangle _Bounds;
        /// <summary>
        /// Outer coordinates of scrollbar.
        /// Setting a coordinate will detect orientation by Width/Height relation.
        /// </summary>
        public override Rectangle ActiveBounds
        {
            get
            {
                this.InnerBoundsCheck();
                return this.Bounds;
            }
            set { this.Bounds = value; }
        }
        /// <summary>
        /// Oriantation of scrollbar
        /// </summary>
        public Orientation Orientation
        {
            get { return this._Orientation; }
            set
            {
                this._Orientation = value;
                this.InnerBoundsReset();
            }
        } private Orientation _Orientation = Orientation.Horizontal;
        protected void SubItemsInit()
        {
            this.SubItemDict = new Dictionary<ScrollBarItemType, ScrollBarSubItem>();
            // Order of items is liable:
            this.SubItemDict.Add(ScrollBarItemType.AllArea, new ScrollBarSubItem(this, ScrollBarItemType.AllArea));
            this.SubItemDict.Add(ScrollBarItemType.MinArea, new ScrollBarSubItem(this, ScrollBarItemType.MinArea, true));
            this.SubItemDict.Add(ScrollBarItemType.MaxArea, new ScrollBarSubItem(this, ScrollBarItemType.MaxArea, true));
            this.SubItemDict.Add(ScrollBarItemType.MinArrow, new ScrollBarSubItem(this, ScrollBarItemType.MinArrow, true));
            this.SubItemDict.Add(ScrollBarItemType.MaxArrow, new ScrollBarSubItem(this, ScrollBarItemType.MaxArrow, true));
            this.SubItemDict.Add(ScrollBarItemType.Thumb, new ScrollBarSubItem(this, ScrollBarItemType.Thumb, true));
            this.SubItemDict.Add(ScrollBarItemType.CenterThumb, new ScrollBarSubItem(this, ScrollBarItemType.CenterThumb));
        }
        protected void InnerBoundsReset()
        {
            this.IsInnerAreaValid = false;
        }
        protected ScrollBarSubItem SubItemMinArrow { get { return this.SubItemDict[ScrollBarItemType.MinArrow]; } }
        protected ScrollBarSubItem SubItemMinArea { get { return this.SubItemDict[ScrollBarItemType.MinArea]; } }
        protected ScrollBarSubItem SubItemThumb { get { return this.SubItemDict[ScrollBarItemType.Thumb]; } }
        protected ScrollBarSubItem SubItemMaxArea { get { return this.SubItemDict[ScrollBarItemType.MaxArea]; } }
        protected ScrollBarSubItem SubItemMaxArrow { get { return this.SubItemDict[ScrollBarItemType.MaxArrow]; } }
        protected ScrollBarSubItem SubItemAllArea { get { return this.SubItemDict[ScrollBarItemType.AllArea]; } }
        protected ScrollBarSubItem SubItemCenterThumb { get { return this.SubItemDict[ScrollBarItemType.CenterThumb]; } }
        /// <summary>
        /// Returns a (cached) array of currently active items
        /// </summary>
        /// <returns></returns>
        protected IEnumerable<IInteractiveSubItem> GetSubItems()
        {
            if (this.SubItemArray == null || this.LastStyle != this.Style)
            {
                this.SubItemArray = this.SubItemDict.Values.Where(i => i.IsEnabled).ToArray();
                this.LastStyle = this.Style;
            }
            return this.SubItemArray;
        }
        protected Dictionary<ScrollBarItemType, ScrollBarSubItem> SubItemDict;
        protected IInteractiveSubItem[] SubItemArray;
        protected bool IsInnerAreaValid { get; private set; }
        protected GInteractiveStyles LastStyle = GInteractiveStyles.None;
        #endregion
        #region Value, ValueTotal
        /// <summary>
        /// Logical value: total size of document (Begin, End)
        /// </summary>
        public SizeRange ValueTotal
        {
            get { return this._ValueTotal; }
            set { this._ValueTotal = value; this.Value = _ValueAlign(this._Value); }
        }
        /// <summary>
        /// Logical value: position of first visible pixel of document in visible part
        /// </summary>
        public SizeRange Value
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, true, true); }
        }
        /// <summary>
        /// Logical value: position of first visible pixel of document in visible part.
        /// Silent = setting an value to this property do not trigger event ValueChanged.
        /// </summary>
        protected SizeRange ValueSilent
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, true, false); }
        }
        /// <summary>
        /// Logical value: position of first visible pixel of document in visible part.
        /// Drag = setting an value to this property do not reset inner bounds (InnerBoundsReset())
        /// </summary>
        protected SizeRange ValueDrag
        {
            get { return this._Value; }
            set { this._ValueSet(value, true, false, true); }
        }
        private SizeRange _Value;
        private SizeRange _ValueTotal;
        /// <summary>
        /// Store specified value to this._Value.
        /// Perform optional _AlignValue, ScrollDataValidate, InnerBoundsReset, OnValueChanged.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="align"></param>
        /// <param name="scroll"></param>
        /// <param name="callInnerReset"></param>
        /// <param name="callEvents"></param>
        protected void _ValueSet(SizeRange value, bool align, bool callInnerReset, bool callEvents)
        {
            if (value == null) return;
            if (align)
                value = this._ValueAlign(value);
            bool isEqual = (value == this._Value);
            if (!isEqual)
            {
                this._Value = value;
                if (callInnerReset)
                    this.InnerBoundsReset();
                if (callEvents)
                    this._OnValueChanged();
            }
        }
        /// <summary>
        /// Align specified value to this.ValueTotal
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private SizeRange _ValueAlign(SizeRange value)
        {
            SizeRange valueTotal = this.ValueTotal;
            if (value == null || valueTotal== null) return value;
            if (!value.IsFilled || !valueTotal.IsFilled) return value;
            if (value.Size.Value <= 0m) return value;
            if (valueTotal.Size.Value <= 0m) return value;

            decimal size = value.Size.Value;
            decimal begin = value.Begin.Value;
            if (begin < valueTotal.Begin.Value)
                begin = valueTotal.Begin.Value;
            if (begin + size > valueTotal.End.Value)
                size = valueTotal.End.Value - begin;

            return SizeRange.CreateFromBeginSize(begin, size);
        }
        /// <summary>
        /// Value for small change (by clicking to Up/Down arrow).
        /// Value is ratio of current this.Value.Size.
        /// Default = 0.10m.
        /// Value is aligned to range 0 to 1 (include).
        /// </summary>
        public decimal? ValueSmallChange
        {
            get { return this._ValueSmallChange; }
            set { this._ValueSmallChange = (value < 0m ? 0m : (value > 1m ? 1m : value)); }
        } private decimal? _ValueSmallChange;
        /// <summary>Current Small change = _ValueSmallChange * _Value.Size</summary>
        protected decimal ValueSmallChangeCurrent { get { return ((this._Value != null && this._Value.IsFilled && this._Value.Size.Value > 0m) ? ((this._ValueSmallChange.HasValue ? this._ValueSmallChange.Value : 0.10m) * this._Value.Size.Value) : 1.00m); } }
        /// <summary>
        /// Value for big change (by clicking to Up/Down area).
        /// Value is ratio of current this.Value.Size.
        /// Default = 0.90m.
        /// Value is aligned to range 0 to 1 (include).
        /// </summary>
        public decimal? ValueBigChange
        {
            get { return this._ValueBigChange; }
            set { this._ValueBigChange = (value < 0m ? 0m : (value > 1m ? 1m : value)); }
        } private decimal? _ValueBigChange;
        /// <summary>Current Big change = _ValueBigChange * _Value.Size</summary>
        protected decimal ValueBigChangeCurrent { get { return ((this._Value != null && this._Value.IsFilled && this._Value.Size.Value > 0m) ? ((this._ValueBigChange.HasValue ? this._ValueBigChange.Value : 0.90m) * this._Value.Size.Value) : 10.00m); } }
        /// <summary>
        /// After value changed
        /// </summary>
        public event EventHandler ValueChanged;
        private void _OnValueChanged()
        {
            this.OnValueChanged();
            if (this.ValueChanged != null)
                this.ValueChanged(this, EventArgs.Empty);
        }
        protected virtual void OnValueChanged() { }
        #endregion
        #region InnerBounds calculator
        /// <summary>
        /// Ensure validity for InnerBounds (when IsInnerAreaValid = false)
        /// </summary>
        /// <returns></returns>
        protected bool InnerBoundsCheck()
        {
            if (this.IsInnerAreaValid) return true;
            switch (this._Orientation)
            {
                case System.Windows.Forms.Orientation.Horizontal:
                    this.InnerBoundsCalculateH();
                    break;
                case System.Windows.Forms.Orientation.Vertical:
                    this.InnerBoundsCalculateV();
                    break;
            }
            this.SubItemArray = null;
            return this.IsInnerAreaValid;
        }
        /// <summary>
        /// Calculate inner area for horizontal orientation
        /// </summary>
        protected void InnerBoundsCalculateH()
        {
            Rectangle r = this._Bounds;
            int buttonLength;
            if (!this.InnerBoundsCalculateData(0, r.Width, r.Height, out buttonLength))
                return;

            int begin = 0;
            int size = r.Height - 1;
            int areaEnd = r.Width - 1 - buttonLength;
            int current = 0;
            this.SubItemMinArrow.RelativeBoundsSet(new Rectangle(current, begin, buttonLength, size));
            current = areaEnd;
            this.SubItemMaxArrow.RelativeBoundsSet(new Rectangle(current, begin, buttonLength, size));

            this.SubItemAllArea.RelativeBoundsSet(new Rectangle((int)this._PixelTotal.Begin.Value, begin, (int)this._PixelTotal.Size.Value, size));
            this.SubItemMinArea.RelativeBoundsSet(new Rectangle((int)this._PixelTotal.Begin.Value, begin, (int)(this._PixelThumb.Begin.Value - this._PixelTotal.Begin.Value), size));
            this.SubItemMaxArea.RelativeBoundsSet(new Rectangle((int)this._PixelThumb.End.Value, begin, (int)(this._PixelTotal.End.Value - this._PixelThumb.End.Value), size));
            this.SubItemThumb.RelativeBoundsSet(new Rectangle((int)this._PixelThumb.Begin.Value, begin, (int)this._PixelThumb.Size.Value, size));

            this.SubItemThumb.DragCursorType = SysCursorType.NoMoveHoriz;

            // this.SubItemCenterThumb.RelativeBoundsSet(new Rectangle(thumbBegin + (thumbEnd - thumbBegin - size) / 2, begin, size, size));

            this.IsInnerAreaValid = true;
        }
        /// <summary>
        /// Calculate inner area for vertical orientation
        /// </summary>
        protected void InnerBoundsCalculateV()
        {
            Rectangle r = this._Bounds;
            int buttonLength;
            if (!this.InnerBoundsCalculateData(0, r.Height, r.Width, out buttonLength))
                return;

            int begin = 0;
            int size = r.Width - 1;
            int areaEnd = r.Height - 1 - buttonLength;
            int current = 0;
            this.SubItemMinArrow.RelativeBoundsSet(new Rectangle(begin, current, size, buttonLength));
            current = areaEnd;
            this.SubItemMaxArrow.RelativeBoundsSet(new Rectangle(begin, current, size, buttonLength));

            this.SubItemAllArea.RelativeBoundsSet(new Rectangle(begin, (int)this._PixelTotal.Begin.Value, size, (int)this._PixelTotal.Size.Value));
            this.SubItemMinArea.RelativeBoundsSet(new Rectangle(begin, (int)this._PixelTotal.Begin.Value, size, (int)(this._Pixel.Center.Value - this._PixelTotal.Begin.Value)));
            this.SubItemMaxArea.RelativeBoundsSet(new Rectangle(begin, (int)this._Pixel.Center.Value, size, (int)(this._PixelTotal.End.Value - this._Pixel.Center.Value)));
            this.SubItemThumb.RelativeBoundsSet(new Rectangle(begin, (int)this._PixelThumb.Begin.Value, size, (int)this._PixelThumb.Size.Value));

            this.SubItemThumb.DragCursorType = SysCursorType.NoMoveVert;

            // this.SubItemCenterThumb.RelativeBoundsSet(new Rectangle(thumbBegin + (thumbEnd - thumbBegin - size) / 2, begin, size, size));

            this.IsInnerAreaValid = true;
        }
        private bool InnerBoundsCalculateData(int begin, int length, int width, out int buttonLength)
        {
            this._PixelReset();
            buttonLength = 0;
            if (this._ValueTotal == null || !this._ValueTotal.IsFilled) return false;
            if (this._Value == null || !this._Value.IsFilled || this._Value.Size <= 0m) return false;
            if (length < 10) return false;

            buttonLength = (length > 25 ? ((length > (5 * width)) ? width : length / 5) : 0);
            this._PixelTotal = SizeRange.CreateFromBeginSize(begin + buttonLength, length - (2 * buttonLength));
            this._PixelTrack = this._PixelTotal.Clone;
            this._Scale = this._ValueTotal.Size.Value / this._PixelTrack.Size.Value;
            this._Pixel = this._GetPixelFromValue(this._Value, false);
            decimal minThumbSize = (this._PixelTotal.Size.Value < 24m ? this._PixelTotal.Size.Value / 3m : 8m);
            if (this._Pixel.Size.Value >= minThumbSize)
            {   // Thumb size is correct => no offset, no inflate _PixelThumb, no shrink _PixelTrack:
                this._PixelThumbOffset = 0m;
                this._PixelThumb = this._Pixel.Clone;
            }
            else
            {   // Thumbs theoretical size is smaller than 8px, we must inflate thumb to 8px (or 1/3 of _PixelTotal.Size):
                this._PixelThumbOffset = (minThumbSize - this._Pixel.Size.Value) / 2m;
                this._PixelTrack = new SizeRange(this._PixelTotal.Begin.Value + this._PixelThumbOffset, this._PixelTotal.End.Value - this._PixelThumbOffset);
                this._Scale = this._ValueTotal.Size.Value / this._PixelTrack.Size.Value;
                this._Pixel = this._GetPixelFromValue(this._Value, false);
                this._PixelThumb = new SizeRange(this._Pixel.Begin.Value - this._PixelThumbOffset, this._Pixel.End.Value + this._PixelThumbOffset);
            }

            // Round this._PixelThumb to Int32:
            decimal thumbBegin = this._PixelThumb.Begin.Value;
            decimal roundBegin = Math.Round(this._PixelThumb.Begin.Value, 0);
            decimal roundSize = Math.Round(this._PixelThumb.Size.Value, 0);
            this._PixelThumb = SizeRange.CreateFromBeginSize(roundBegin, roundSize);
            // this._PixelThumbOffset += (thumbBegin - roundBegin);    // Hezký úmysl, ale musely by se předělat metody CalculateBoundsInteractiveDragH() a CalculateBoundsInteractiveDragV() tak, aby ... co vlastně? No, jednoduše to nefungovalo.

            return true;
        }
        private void _PixelReset()
        {
            this._PixelTotal = null;
            this._PixelTrack = null;
            this._Pixel = null;
            this._PixelThumb = null;
            this._PixelThumbOffset = 0m;
            this._Scale = 0m;
        }
        /// <summary>
        /// Range of pixel of whole scrollable area (area for thumb move).
        /// Not for calculations, only for InnerArea (interactivity and paint).
        /// Integer numbers (as decimal, but without fractions).
        /// </summary>
        private SizeRange _PixelTotal;
        /// <summary>
        /// Range of pixel of theoretical thumb position.
        /// Theoretical value, for calculations.
        /// Real thumb can be bigger.
        /// Decimal numbers (with fractions).
        /// </summary>
        private SizeRange _Pixel;
        /// <summary>
        /// Range of pixel of real thumb position.
        /// When theoretical value in this._Pixel has .Size small than 8 pixel, then _PixelThumb has size 8 pixel, and is "shifted" = centered around this._Pixel.
        /// Not for calculations, only for InnerArea (interactivity and paint).
        /// Integer numbers (as decimal, but without fractions).
        /// </summary>
        private SizeRange _PixelThumb;
        /// <summary>
        /// Area for scrolling of theoretical thumb (this._Pixel).
        /// When theoretical value in this._Pixel has .Size small than 8 pixel, and _PixelThumb is inflate, then usable area for track is smaller (about this inflate).
        /// Theoretical value, for calculations.
        /// Decimal numbers (with fractions).
        /// </summary>
        private SizeRange _PixelTrack;
        /// <summary>
        /// Offset = (this._Pixel.Begin - this._PixelThumb.Begin).
        /// For interactive change (Thumb.Dragging), where are from physical coordinates of thumb (_PixelThumb) calculated theoretical position (_Pixel).
        /// Calculations: _Pixel.Begin = (_PixelThumb.Begin + Offset); _PixelThumb.Begin = (_Pixel.Begin + Offset).
        /// It also includes the difference of rounding (in the same sense).
        /// </summary>
        private decimal _PixelThumbOffset;
        #endregion
        #region Convert Value to/from Pixel
        /// <summary>
        /// Return SizeRange in pixels from value (booth in absolute coordinates)
        /// </summary>
        /// <param name="value"></param>
        /// <param name="roundToInt"></param>
        /// <returns></returns>
        private SizeRange _GetPixelFromValue(SizeRange value, bool roundToInt)
        {
            decimal begin = _GetPixelFromValue(value.Begin.Value, roundToInt);
            decimal size = _GetPixelDistanceFromValue(value.Size.Value, roundToInt);
            return SizeRange.CreateFromBeginSize(begin, size);
        }
        /// <summary>
        /// Return SizeRange in pixels from value (booth in absolute coordinates)
        /// </summary>
        /// <param name="value"></param>
        /// <param name="roundToInt"></param>
        /// <returns></returns>
        private SizeRange _GetValueFromPixel(SizeRange value)
        {
            decimal begin = _GetValueFromPixel(value.Begin.Value);
            decimal size = _GetValueDistanceFromPixel(value.Size.Value);
            return SizeRange.CreateFromBeginSize(begin, size);
        }
        /// <summary>
        /// Return absolute pixel for specified absolute value.
        /// Result can be rounded or unrounded, ba parameter roundToInt.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private decimal _GetPixelFromValue(decimal value, bool roundToInt)
        {
            if (!this._IsCalcReady) return 0;
            decimal pixel = (value - this._ValueBegin.Value) / this._Scale.Value + this._PixelBegin.Value;
            return (roundToInt ? Math.Round(pixel, 0) : pixel);
        }
        /// <summary>
        /// Return absolute value for specified absolute pixel.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private decimal _GetValueFromPixel(decimal pixel)
        {
            if (!this._IsCalcReady) return 0m;
            decimal value = (pixel - this._PixelBegin.Value) * this._Scale.Value + this._ValueBegin.Value;
            return value;
        }
        /// <summary>
        /// Return pixel distance for specified value distance.
        /// Result can be rounded or unrounded, ba parameter roundToInt.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private decimal _GetPixelDistanceFromValue(decimal value, bool roundToInt)
        {
            if (!this._IsCalcReady) return 0m;
            decimal distance = value / this._Scale.Value;
            return (roundToInt ? Math.Round(distance, 0) : distance);
        }
        /// <summary>
        /// Return value distance for specified pixel distance.
        /// </summary>
        /// <param name="pixel"></param>
        /// <returns></returns>
        private decimal _GetValueDistanceFromPixel(decimal pixel)
        {
            if (!this._IsCalcReady) return 0m;
            decimal distance = pixel * this._Scale.Value;
            return distance;
        }
        /// <summary>
        /// true when calculator is ready (is filled _ValueTotal, _PixelTotal and _Scale)
        /// </summary>
        private bool _IsCalcReady
        {
            get
            {
                return (this._ValueTotal != null && this._ValueTotal.IsFilled &&
                        this._PixelTotal != null && this._PixelTotal.IsFilled &&
                        this._Scale.HasValue && this._Scale.Value > 0m);
            }
        }
        private decimal? _ValueBegin { get { return this._ValueTotal.Begin; } }
        private decimal? _PixelBegin { get { return this._PixelTrack.Begin; } }
        /// <summary>
        /// Scale = value / pixel (numbers of mm to 1 pixel)
        /// </summary>
        private decimal? _Scale;
        #endregion
        #region SubItems, inner areas
        #region class ScrollBarSubItem : class for sub-items in ScrollBar
        protected class ScrollBarSubItem : IInteractiveSubItem
        {
            public ScrollBarSubItem(GScrollBar owner, ScrollBarItemType itemType)
            {
                this.Owner = owner;
                this.ItemType = itemType;
                this.OverCursorType = null;
                this.DragCursorType = null;
                this.RelativeActiveBounds = Rectangle.Empty;
            }
            public ScrollBarSubItem(GScrollBar owner, ScrollBarItemType itemType, bool isEnabled)
                : this(owner, itemType)
            {
                this.IsEnabled = isEnabled;
            }
            public ScrollBarSubItem(GScrollBar owner, ScrollBarItemType itemType, bool isEnabled, SysCursorType? dragCursorType)
                : this(owner, itemType)
            {
                this.IsEnabled = isEnabled;
                this.DragCursorType = dragCursorType;
            }
            public override string ToString()
            {
                return this.ItemType.ToString();
            }
            /// <summary>
            /// Owner of this SubItem = an ScrollBar
            /// </summary>
            public GScrollBar Owner { get; private set; }
            /// <summary>
            /// Type of SubItem
            /// </summary>
            public ScrollBarItemType ItemType { get; private set; }
            /// <summary>
            /// Cursor type in MouseOver state for this SubItem
            /// </summary>
            public SysCursorType? OverCursorType { get; set; }
            /// <summary>
            /// Cursor type in MouseDrag state for this SubItem
            /// </summary>
            public SysCursorType? DragCursorType { get; set; }
            /// <summary>
            /// Is this item enabled for draw and interactive operations?
            /// </summary>
            public bool IsEnabled { get; set; }
            /// <summary>
            /// This SubItem can be dragged?
            /// </summary>
            public bool CanDrag { get { return (this.IsEnabled && this.ItemType == ScrollBarItemType.Thumb); } }
            /// <summary>
            /// Is this SubItem currently active SubItem of Owner?
            /// </summary>
            public bool IsCurrentItem { get { return (this.ItemType == this.Owner.CurrentSubItemType); } }
            /// <summary>
            /// Interactive State of this item
            /// </summary>
            public GInteractiveState ItemState { get { return (this.IsCurrentItem ? this.Owner.CurrentState : GInteractiveState.None); } }
            /// <summary>
            /// Is this SubItem in MouseActive state?
            /// </summary>
            public bool IsMouseActive { get { GInteractiveState s = this.ItemState; return (s == GInteractiveState.Mouse || s == GInteractiveState.LeftDown || s == GInteractiveState.RightDown || s == GInteractiveState.LeftDrag || s == GInteractiveState.RightDrag); } }
            /// <summary>
            /// Current change of logical value after click on this SubItem
            /// </summary>
            public decimal CurrentChangeValue
            {
                get
                {
                    decimal smallChange = this.Owner.ValueSmallChangeCurrent;
                    decimal bigChange = this.Owner.ValueBigChangeCurrent;
                    switch (this.ItemType)
                    {
                        case ScrollBarItemType.MinArrow: return -smallChange;
                        case ScrollBarItemType.MinArea: return -bigChange;
                        case ScrollBarItemType.MaxArea: return bigChange;
                        case ScrollBarItemType.MaxArrow: return smallChange;
                    }
                    return 0m;
                }
            }
            /// <summary>
            /// Store relative coordinates into this object:
            /// a) shift by this.Owner.Bounds.Location as VisibleBounds
            /// b) Directly store as RelativeActiveBounds
            /// </summary>
            /// <param name="relativeBounds"></param>
            internal bool RelativeBoundsSet(Rectangle relativeBounds)
            {
                Rectangle visibleBounds = relativeBounds.ShiftBy(this.Owner.Bounds.Location);
                if (visibleBounds == this.VisibleBounds) return false;
                this.VisibleBounds = visibleBounds;
                this.RelativeActiveBounds = relativeBounds;
                return true;
            }
            /// <summary>
            /// Current visible area, absolute in Control.Location.
            /// </summary>
            public Rectangle VisibleBounds { get; private set; }
            /// <summary>
            /// Current active area (only get), relative to Owner.Location.
            /// Area is settable by this.RelativeBoundsSet().
            /// </summary>
            public Rectangle RelativeActiveBounds { get; private set; }
            Rectangle IInteractiveSubItem.RelativeActiveBounds { get { return this.RelativeActiveBounds; } }
            bool IInteractiveSubItem.IsVisible { get { return true; } }
            bool IInteractiveSubItem.HoldTheMouseAttention { get { return false; } }
        }
        #endregion
        protected override IEnumerable<IInteractiveSubItem> SubItems { get { return this.GetSubItems(); } }
        protected enum ScrollBarItemType : int
        {
            None = 0,
            MinArrow,
            MinArea,
            Thumb,
            MaxArea,
            MaxArrow,
            AllArea,
            CenterThumb
        }
        #endregion
        #endregion
        #region Interactivity
        protected override void StateChanged(GInteractiveChangeStateArgs e)
        {
            ScrollBarSubItem subItem = e.CurrentSubItem as ScrollBarSubItem;
            ScrollBarItemType it = (subItem == null ? ScrollBarItemType.None : subItem.ItemType);
            switch (e.ChangeState)
            {
                case GInteractiveChangeState.MouseEnter:
                    this.CurrentSubItem = null;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.MouseLeave:
                    this.CurrentSubItem = null;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.MouseEnterSubItem:
                    this.CurrentSubItem = subItem;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    if (subItem != null)
                        e.RequiredCursorType = subItem.OverCursorType;
                    break;
                case GInteractiveChangeState.MouseLeaveSubItem:
                    this.CurrentSubItem = null;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.RequiredCursorType = SysCursorType.Default;
                    break;
                case GInteractiveChangeState.MouseOver:
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.LeftDown:
                    if (subItem != null && subItem.IsEnabled && (it == ScrollBarItemType.MinArrow || it == ScrollBarItemType.MinArea || it == ScrollBarItemType.MaxArea || it == ScrollBarItemType.MaxArrow))
                        this.CalculateBoundsInteractiveClick(subItem);
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.LeftUp:
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.LeftClick:
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
                case GInteractiveChangeState.LeftDragBegin:
                    if (subItem != null && subItem.CanDrag)
                    {
                        this.CurrentSubItem = subItem;
                        this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                        e.RequiredCursorType = subItem.DragCursorType;
                        e.UserDragPoint = subItem.RelativeActiveBounds.Location;
                    }
                    break;
                case GInteractiveChangeState.LeftDragMove:
                    if (this.CurrentSubItem != null && this.CurrentSubItem.CanDrag && e.UserDragPoint.HasValue)
                    {
                        switch (this.Orientation)
                        {
                            case System.Windows.Forms.Orientation.Horizontal:
                                this.CalculateBoundsInteractiveDragH(this.CurrentSubItem, e.UserDragPoint.Value);
                                break;
                            case System.Windows.Forms.Orientation.Vertical:
                                this.CalculateBoundsInteractiveDragV(this.CurrentSubItem, e.UserDragPoint.Value);
                                break;
                        }
                    }
                    break;
                case GInteractiveChangeState.LeftDragEnd:
                    if (this.CurrentSubItem != null && this.CurrentSubItem.CanDrag)
                    {
                        this.RepaintAllItems = true;
                        this.InnerBoundsReset();
                        e.RequiredCursorType = (subItem.OverCursorType.HasValue ? subItem.OverCursorType.Value : SysCursorType.Default);
                    }
                    break;
                default:
                    var s = e.ChangeState;
                    break;
            }
        }
        /// <summary>
        /// Calculate new bounds and values after LeftClick on subItem (MinArrow, MinArea, MaxArea, MaxArrow)
        /// </summary>
        /// <param name="subItem"></param>
        protected void CalculateBoundsInteractiveClick(ScrollBarSubItem subItem)
        {
            if (this._Value == null || !this._Value.IsFilled) return;
            decimal change = subItem.CurrentChangeValue;
            if (change == 0m) return;
            decimal begin = this._Value.Begin.Value + change;
            this.Value = SizeRange.CreateFromBeginSize(begin, this._Value.Size.Value);
        }
        /// <summary>
        /// Calculate new bounds and values during interactive moving (Drag) on Horizontal scrollbar
        /// </summary>
        /// <param name="subItem"></param>
        /// <param name="point"></param>
        protected void CalculateBoundsInteractiveDragH(ScrollBarSubItem subItem, Point point)
        {
            Rectangle area = this.SubItemAllArea.RelativeActiveBounds;
            Rectangle item = subItem.RelativeActiveBounds;

            int thumb = CalculateInteractiveThumbBegin(point.X);

            if (subItem.RelativeBoundsSet(new Rectangle(thumb, item.Y, item.Width, item.Height)))
                this.RepaintToLayers = GInteractiveDrawLayer.Standard;
        }
        /// <summary>
        /// Calculate new bounds and values during interactive moving (Drag) on Vertical scrollbar
        /// </summary>
        /// <param name="subItem"></param>
        /// <param name="p"></param>
        protected void CalculateBoundsInteractiveDragV(ScrollBarSubItem subItem, Point point)
        {
            Rectangle area = this.SubItemAllArea.RelativeActiveBounds;
            Rectangle item = subItem.RelativeActiveBounds;

            int thumb = CalculateInteractiveThumbBegin(point.Y);

            if (subItem.RelativeBoundsSet(new Rectangle(item.X, thumb, item.Width, item.Height)))
                this.RepaintToLayers = GInteractiveDrawLayer.Standard;
        }
        protected int CalculateInteractiveThumbBegin(int thumbDragged)
        {
            decimal thumb = (decimal)thumbDragged;

            if ((thumb + this._PixelThumb.Size.Value) > this._PixelTotal.End.Value) thumb = (this._PixelTotal.End.Value - this._PixelThumb.Size.Value);
            if (thumb < this._PixelTotal.Begin.Value) thumb = this._PixelTotal.Begin.Value;

            decimal pixel = thumb + this._PixelThumbOffset;
            decimal begin = this._GetValueFromPixel(pixel);
            this.ValueDrag = SizeRange.CreateFromBeginSize(begin, this._Value.Size.Value);

            return (int)thumb;
        }
        protected ScrollBarItemType CurrentSubItemType { get { return (this.CurrentSubItem != null ? this.CurrentSubItem.ItemType : ScrollBarItemType.None); } }
        protected ScrollBarSubItem CurrentSubItem { get; set; }
        #endregion
        #region Draw
        /// <summary>
        /// Draw this scrollbar
        /// </summary>
        /// <param name="e"></param>
        protected override void Draw(GInteractiveDrawArgs e)
        {
            e.Graphics.FillRectangle(SystemBrushes.AppWorkspace, this.ActiveBounds);
            if (!this.InnerBoundsCheck()) return;

            this.DrawOneBase(e.Graphics, this.SubItemAllArea, SystemColors.AppWorkspace);
            if (this.SubItemMinArea.IsMouseActive)
                this.DrawOneBase(e.Graphics, this.SubItemMinArea, SystemColors.AppWorkspace);
            if (this.SubItemMaxArea.IsMouseActive)
                this.DrawOneBase(e.Graphics, this.SubItemMaxArea, SystemColors.AppWorkspace);
            this.DrawOneButton(e.Graphics, this.SubItemMinArrow, SystemColors.ButtonFace, 0);
            this.DrawOneButton(e.Graphics, this.SubItemThumb, SystemColors.ButtonFace, 1);
            this.DrawOneButton(e.Graphics, this.SubItemMaxArrow, SystemColors.ButtonFace, 0);
        }
        protected void DrawOneBase(Graphics graphics, ScrollBarSubItem subItem, Color color)
        {
            if (subItem.IsEnabled)
                GPainter.DrawAreaBase(graphics, subItem.VisibleBounds, color, subItem.ItemState, this.Orientation, null, null);
        }
        protected void DrawOneButton(Graphics graphics, ScrollBarSubItem subItem, Color color, int roundCorner)
        {
            Rectangle bounds = subItem.VisibleBounds;
            if (subItem.ItemState == GInteractiveState.LeftDown || subItem.ItemState == GInteractiveState.LeftDrag || subItem.ItemState == GInteractiveState.RightDown)
            {
                bounds = bounds.ShiftBy(1, 1);
            }
            if (subItem.IsEnabled)
                GPainter.DrawButtonBase(graphics, bounds, color, subItem.ItemState, this.Orientation, roundCorner, null, null);
        }
        #endregion
    }
}
